﻿using CommandLinex;
using N_m3u8DL_RE.Common.Entity;
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Common.Util;
using N_m3u8DL_RE.Config;
using N_m3u8DL_RE.DownloadManager;
using N_m3u8DL_RE.Entity;
using N_m3u8DL_RE.Enums;
using N_m3u8DL_RE.Parser;
using N_m3u8DL_RE.Parser.Config;
using N_m3u8DL_RE.Util;
using System.Globalization;
using System.Net;
using System.Text;

namespace N_m3u8DL_RE;

public class Program
{
    static async Task Main(string[] args)
    {
        Console.CancelKeyPress += Console_CancelKeyPress;
        ServicePointManager.DefaultConnectionLimit = 1024;
        try { Console.CursorVisible = true; } catch { }
        ResString.CurrentLoc = "zh-CN";
        try
        {
            CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(ResString.CurrentLoc);
            Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(ResString.CurrentLoc);
            Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(ResString.CurrentLoc);
        }
        catch
        {
        }
        //bool isBreak = false;
        ////开始控制进度条和进度变化
        //for (int i = 0; ++i <= 100;)
        //{
        //    if (Console.KeyAvailable && System.Console.ReadKey(true).Key == ConsoleKey.Enter)
        //    {
        //        isBreak = true; break;
        //    }
        //    Console.SetCursorPosition(0, 2);
        //    Console.Write("{0}a%", i);
        //    //模拟实际工作中的延迟,否则进度太快
        //    System.Threading.Thread.Sleep(100);
        //}
        //Console.ReadKey(true);

        // 添加换行符以显示进度条完成后的新行
        //Console.WriteLine("\n任务完成！");
        var parser = new CommandLineParser();
        //args = new string[]
        //{
        //    "--input","https://vip.ffzy-online.com/20221023/2426_23d96bec/index.m3u8",
        //    "--thread-count","10",
        //    "--del-after-done","True",
        //    "--check-segments-count","True",
        //    "--download-retry-count","3",
        //    "--http-request-timeout","12",
        //    "--save-dir","D:\\subtitle\\待处理\\轻熟女时代",
        //    "--save-name","01",
        //    "--tmp-dir","D:\\subtitle\\待处理\\轻熟女时代\\01"
        //};
        var options = parser.Parse<DownloadArgument>(args);
        //await CommandInvoker.InvokeArgs(args, DoWorkAsync);
        await DoWorkAsync(options);
    }

    private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e)
    {
        Logger.Warn("Force Exit...");
        try
        {
            Console.CursorVisible = true;
            if (!OperatingSystem.IsWindows())
                System.Diagnostics.Process.Start("tput", "cnorm");
        }
        catch { }
        Environment.Exit(0);
    }

    static int GetOrder(StreamSpec streamSpec)
    {
        if (streamSpec.Channels == null) return 0;

        var str = streamSpec.Channels.Split('/')[0];
        return int.TryParse(str, out var order) ? order : 0;
    }

    static async Task DoWorkAsync(DownloadArgument argment)
    {
        HTTPUtil.AppHttpClient.Timeout = TimeSpan.FromSeconds(argment.HttpRequestTimeout);


        // 检测更新
        Logger.IsWriteFile = false;
        Logger.LogLevel = argment.LogLevel;
        HTTPUtil.HttpClientHandler.UseProxy = false;

        // 预先检查ffmpeg
        argment.FFmpegBinaryPath ??= GlobalUtil.FindExecutable("ffmpeg");

        if (string.IsNullOrEmpty(argment.FFmpegBinaryPath) || !File.Exists(argment.FFmpegBinaryPath))
        {
            throw new FileNotFoundException(ResString.ffmpegNotFound);
        }

        //Logger.Extra($"ffmpeg => {argment.FFmpegBinaryPath}");

        // 预先检查mkvmerge
        if (argment is { MuxOptions.UseMkvmerge: true, MuxAfterDone: true })
        {
            argment.MkvmergeBinaryPath ??= GlobalUtil.FindExecutable("mkvmerge");
            if (string.IsNullOrEmpty(argment.MkvmergeBinaryPath) || !File.Exists(argment.MkvmergeBinaryPath))
            {
                throw new FileNotFoundException(ResString.mkvmergeNotFound);
            }
            Logger.Extra($"mkvmerge => {argment.MkvmergeBinaryPath}");
        }

        // 预先检查
        if (argment.Keys is { Length: > 0 } || argment.KeyTextFile != null)
        {
            if (!string.IsNullOrEmpty(argment.DecryptionBinaryPath) && !File.Exists(argment.DecryptionBinaryPath))
            {
                throw new FileNotFoundException(argment.DecryptionBinaryPath);
            }
            switch (argment.DecryptionEngine)
            {
                case DecryptEngine.SHAKA_PACKAGER:
                    {
                        var file = GlobalUtil.FindExecutable("shaka-packager");
                        var file2 = GlobalUtil.FindExecutable("packager-linux-x64");
                        var file3 = GlobalUtil.FindExecutable("packager-osx-x64");
                        var file4 = GlobalUtil.FindExecutable("packager-win-x64");
                        if (file == null && file2 == null && file3 == null && file4 == null)
                            throw new FileNotFoundException(ResString.shakaPackagerNotFound);
                        argment.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
                        Logger.Extra($"shaka-packager => {argment.DecryptionBinaryPath}");
                        break;
                    }
                case DecryptEngine.MP4DECRYPT:
                    {
                        var file = GlobalUtil.FindExecutable("mp4decrypt");
                        if (file == null) throw new FileNotFoundException(ResString.mp4decryptNotFound);
                        argment.DecryptionBinaryPath = file;
                        Logger.Extra($"mp4decrypt => {argment.DecryptionBinaryPath}");
                        break;
                    }
                case DecryptEngine.FFMPEG:
                default:
                    argment.DecryptionBinaryPath = argment.FFmpegBinaryPath;
                    break;
            }
        }

        // 默认的headers
        var headers = new Dictionary<string, string>()
        {
            ["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
        };
        // 添加或替换用户输入的headers
        foreach (var item in argment.Headers)
        {
            headers[item.Key] = item.Value;
            Logger.Extra($"User-Defined Header => {item.Key}: {item.Value}");
        }

        var parserConfig = new ParserConfig()
        {
            AppendUrlParams = argment.AppendUrlParams,
            UrlProcessorArgs = argment.UrlProcessorArgs,
            BaseUrl = argment.BaseUrl!,
            Headers = headers,
            CustomMethod = argment.CustomHLSMethod,
            CustomeKey = argment.CustomHLSKey,
            CustomeIV = argment.CustomHLSIv,
        };

        if (argment.AllowHlsMultiExtMap)
        {
            parserConfig.CustomParserArgs.Add("AllowHlsMultiExtMap", "true");
        }

        var url = argment.Input;
        // 流提取器配置
        var extractor = new StreamExtractor(parserConfig);
        // 从链接加载内容
        await RetryUtil.WebRequestRetryAsync(async () =>
        {
            await extractor.LoadSourceFromUrlAsync(url);
            return true;
        });
        // 解析流信息
        var streams = await extractor.ExtractStreamsAsync();


        // 全部媒体
        var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder).ToList();
        // 基本流
        var basicStreams = lists.Where(x => x.MediaType is null or MediaType.VIDEO).ToList();
        // 可选音频轨道
        var audios = lists.Where(x => x.MediaType == MediaType.AUDIO).ToList();
        // 可选字幕轨道
        var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES).ToList();

        // 尝试从URL或文件读取文件名
        if (string.IsNullOrEmpty(argment.SaveName))
        {
            argment.SaveName = OtherUtil.GetFileNameFromInput(argment.Input);
        }

        // 生成文件夹
        var tmpDir = Path.Combine(argment.TmpDir ?? Environment.CurrentDirectory, $"{argment.SaveName ?? DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}");
        // 记录文件
        extractor.RawFiles["meta.json"] = GlobalUtil.ConvertToJson(lists);
        // 写出文件
        await WriteRawFilesAsync(argment, extractor, tmpDir);

        //Logger.Info(ResString.streamsInfo, lists.Count, basicStreams.Count, audios.Count, subs.Count);

        var selectedStreams = new List<StreamSpec>();
        if (argment.DropVideoFilter != null || argment.DropAudioFilter != null || argment.DropSubtitleFilter != null)
        {
            basicStreams = FilterUtil.DoFilterDrop(basicStreams, argment.DropVideoFilter);
            audios = FilterUtil.DoFilterDrop(audios, argment.DropAudioFilter);
            subs = FilterUtil.DoFilterDrop(subs, argment.DropSubtitleFilter);
            lists = basicStreams.Concat(audios).Concat(subs).ToList();
        }

        if (argment.DropVideoFilter != null) Logger.Extra($"DropVideoFilter => {argment.DropVideoFilter}");
        if (argment.DropAudioFilter != null) Logger.Extra($"DropAudioFilter => {argment.DropAudioFilter}");
        if (argment.DropSubtitleFilter != null) Logger.Extra($"DropSubtitleFilter => {argment.DropSubtitleFilter}");
        if (argment.VideoFilter != null) Logger.Extra($"VideoFilter => {argment.VideoFilter}");
        if (argment.AudioFilter != null) Logger.Extra($"AudioFilter => {argment.AudioFilter}");
        if (argment.SubtitleFilter != null) Logger.Extra($"SubtitleFilter => {argment.SubtitleFilter}");

        if (argment.AutoSelect)
        {
            if (basicStreams.Count != 0)
                selectedStreams.Add(basicStreams.First());
            var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language);
            foreach (var lang in langs)
            {
                selectedStreams.Add(audios.Where(a => a.Language == lang).OrderByDescending(a => a.Bandwidth).ThenByDescending(GetOrder).First());
            }
            selectedStreams.AddRange(subs);
        }
        else if (argment.SubOnly)
        {
            selectedStreams.AddRange(subs);
        }
        else if (argment.VideoFilter != null || argment.AudioFilter != null || argment.SubtitleFilter != null)
        {
            basicStreams = FilterUtil.DoFilterKeep(basicStreams, argment.VideoFilter);
            audios = FilterUtil.DoFilterKeep(audios, argment.AudioFilter);
            subs = FilterUtil.DoFilterKeep(subs, argment.SubtitleFilter);
            selectedStreams = basicStreams.Concat(audios).Concat(subs).ToList();
        }
        else
        {
            // 展示交互式选择框
            selectedStreams = FilterUtil.SelectStreams(lists);
        }

        if (selectedStreams.Count == 0)
            throw new Exception(ResString.noStreamsToDownload);

        // HLS: 选中流中若有没加载出playlist的，加载playlist
        // DASH/MSS: 加载playlist (调用url预处理器)
        if (selectedStreams.Any(s => s.Playlist == null) || extractor.ExtractorType == ExtractorType.MPEG_DASH || extractor.ExtractorType == ExtractorType.MSS)
            await extractor.FetchPlayListAsync(selectedStreams);

        // 无法识别的加密方式，自动开启二进制合并
        if (selectedStreams.Any(s => s.Playlist!.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN))))
        {
            Logger.Warn($"{ResString.autoBinaryMerge3}");
            argment.BinaryMerge = true;
        }

        // 应用用户自定义的广告分片关键字
        FilterUtil.CleanAd(selectedStreams, argment.AdKeywords);

        // 记录文件
        extractor.RawFiles["meta_selected.json"] = GlobalUtil.ConvertToJson(selectedStreams);

        //Logger.Info(ResString.selectedStream);
        foreach (var item in selectedStreams)
        {
            //Logger.Info(item.ToString());
            //Logger.Info(item?.Resolution);
        }

        // 写出文件
        await WriteRawFilesAsync(argment, extractor, tmpDir);

        //Logger.Info(ResString.saveName + $"{argment.SaveName.EscapeMarkup()}");

        // 开始MuxAfterDone后自动使用二进制版
        if (argment is { BinaryMerge: false, MuxAfterDone: true })
        {
            argment.BinaryMerge = true;
            Logger.Warn($"{ResString.autoBinaryMerge6}");
        }

        // 下载配置
        var downloadConfig = new DownloaderConfig()
        {
            MyOptions = argment,
            DirPrefix = tmpDir,
            Headers = parserConfig.Headers, // 使用命令行解析得到的Headers
        };

        var result = false;

        // 开始下载
        var sdm = new SimpleDownloadManager(downloadConfig, selectedStreams, extractor);
        sdm.EXIT = false;
        result = await sdm.StartDownloadAsync();

        if (result)
        {
            Logger.Info("Done");
        }
        else
        {
            Logger.Error("Failed");
            Environment.ExitCode = 1;
        }
        sdm.EXIT = true;
    }

    private static async Task WriteRawFilesAsync(DownloadArgument option, StreamExtractor extractor, string tmpDir)
    {
        // 写出json文件
        if (option.WriteMetaJson)
        {
            //Logger.Info($"tempDir4 {tmpDir}");
            if (!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);
            //Logger.Warn(ResString.writeJson);
            foreach (var item in extractor.RawFiles)
            {
                var file = Path.Combine(tmpDir, item.Key);
                if (!File.Exists(file)) await File.WriteAllTextAsync(file, item.Value, Encoding.UTF8);
            }
        }
    }
}